#define TCP_C
/*----------------------------------------------*
 * include files            	 ������ͷ�ļ�   *
 *----------------------------------------------*/
#if defined(OS_ANDROID)||defined(OS_LINUX_SOC)
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <err.h>
#include <linux/tcp.h>
#include <sys/stat.h>
#elif defined(OS_WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
//user header
#include <libmscommon/mscommon.h>
#include <libmscommon/msstring.h>
#include <libmscommon/msnetwork.h>
#include <libmscommon/msepoll.h>
#include <libmscommon/msenv.h>
#include <libmslog/mslog.h>
#include "msprotocol.h"
/*----------------------------------------------*
 * macros �궨��                                *
 *----------------------------------------------*/
 #define FLAG "TCP"
/*----------------------------------------------*
 * struct 											 *
 *----------------------------------------------*/
/*----------------------------------------------*
 * enum											 *
 *----------------------------------------------*/
/*----------------------------------------------*
 * external variables            �ⲿ����˵��   *
 *----------------------------------------------*/

/*----------------------------------------------*
 * external routine prototypes  �ⲿ����ԭ��˵��*
 *----------------------------------------------*/

/*----------------------------------------------*
 * internal routine prototypes  �ڲ�����ԭ��˵��*
 *----------------------------------------------*/

/*----------------------------------------------*
 * project-wide global variables ȫ�ֱ���       *
 *----------------------------------------------*/

/*----------------------------------------------*
 * module-wide global variables  ģ�鼶����     *
 *----------------------------------------------*/
/*----------------------------------------------*
 * constants  ����                              *
 *----------------------------------------------*/

/*----------------------------------------------*
 * routines' implementations   ������ʵ��       *
 *----------------------------------------------*/ 
 
typedef struct TCPContext {
	int listen_fd;	//tcp socket use
	int msepoll_fd;
} TCPContext;

static void tcp_optinit(URLContext *s){
	OPTVAL_INIT("flag_scanport",s->opt.tcp.flag_scanport, 0,0);	
	//maybe change by user
	OPTVAL_INIT("block",s->opt.tcp.block, 0,0);	
	OPTVAL_INIT_VAILD("listen",s->opt.tcp.listen, 1, 0);		
	OPTVAL_INIT("open_timeout",s->opt.tcp.open_timeout, 0, 50);	
	OPTVAL_INIT("rw_timeout",s->opt.tcp.rw_timeout, 0, msnet_default_timeout_rw);	//Note:client's timeout cannot less than rw_timeout
	OPTVAL_INIT("send_buffer_size",s->opt.tcp.send_buffer_size, 0, -1);
	OPTVAL_INIT("recv_buffer_size",s->opt.tcp.recv_buffer_size, 0, -1);		
	//no need to change
	OPTVAL_INIT_VAILD("nodelay",s->opt.tcp.nodelay, 1, 0);			
	OPTVAL_INIT("reuse",s->opt.tcp.reuse, 0, 1);		
	OPTVAL_INIT("backlog",s->opt.tcp.backlog, 0,24);		
	OPTVAL_BOOL("noprint",s->opt.tcp.flag_noprint, ms_false);	
} 
static int tcp_open(URLContext *s)
{
	if(1==s->is_connected){
		return 0;
	}
	
	tcp_optinit(s);
	msptc_api_urlSplit_urlctt( s);

	MSIPlist msiplist;
	ms_memset(&msiplist,0,sizeof(MSIPlist));
	ms_u08 useip_index=0;
	msnet_api_parseDomainName (s->url_info.hostname, ms_null,&msiplist) ;
	TCPContext * tcp_ctt=ms_null;
ms_retryip:
	if( (msiplist.totalnum>0)&&(useip_index<msiplist.totalnum) ){
		if(!s->opt.tcp.flag_noprint){
			ms_debug("===========url %s,[%d]ipaddr,flag_ipv6:%s,%d",s->url,useip_index,msiplist.ipaddr[useip_index],msiplist.flag_ipv6[useip_index]);
		}
		s->url_info.flag_ipv6=msiplist.flag_ipv6[useip_index];
		ms_sprintfs(s->url_info.hostname, "%s", msiplist.ipaddr[useip_index]);
		useip_index++;
	}else{
		if(s->opt.tcp.flag_scanport){
			goto fail;
		}else{
			ms_errGoto(fail, "used all ip to try,faild,goto end");
		}
	}
	tcp_ctt=s->priv_data=(void *)ms_mallocDes(sizeof(TCPContext),"TCPContext",s->url);
	if(NULL==s->priv_data){
		ms_errGoto(fail, "ms_malloc TCPContext faild(%d,%s)",errno,strerror(errno));
	}
	memset(tcp_ctt,0,sizeof(TCPContext));
	if(!s->opt.tcp.flag_noprint){
		ms_debug("===========Open %s,flags:%d,hostname:%s",s->url,s->flags,s->url_info.hostname);
	}
	struct sockaddr_in6 listen_addr6;
	memset(&listen_addr6, 0, sizeof(listen_addr6));
	listen_addr6.sin6_family = AF_INET6;
	listen_addr6.sin6_port = htons(s->url_info.port);

	struct sockaddr_in listen_addr;
	memset(&listen_addr, 0, sizeof(listen_addr));
	listen_addr.sin_family = AF_INET;
	listen_addr.sin_port = htons(s->url_info.port);
	
	tcp_ctt->msepoll_fd=msepoll_open(2);

	if(1==s->opt.tcp.listen){
		ms_string pmsenv_ptl_hasipv6=msenv_api_get(msenv_ptl_hasipv6,"unknow");
		ms_verbose("%s=%s",msenv_ptl_hasipv6,pmsenv_ptl_hasipv6);
		if(ms_strncmp_saeq(pmsenv_ptl_hasipv6, "false")){
			s->url_info.flag_useipv6=ms_false;
		}else{
			s->url_info.flag_useipv6=ENABLE_IPV6;
		}
	}else{
		s->url_info.flag_useipv6=(ENABLE_IPV6&s->url_info.flag_ipv6);
	}
	if(1==s->opt.tcp.listen){
		if(s->url_info.flag_useipv6){
			listen_addr6.sin6_addr = in6addr_any;
			tcp_ctt->listen_fd = socket(AF_INET6, SOCK_STREAM, 0);	
		}else{
			listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
			tcp_ctt->listen_fd = socket(AF_INET, SOCK_STREAM, 0);		
		}
		if (tcp_ctt->listen_fd < 0){
			ms_errGoto(fail, "socket %s(%d,%s)", s->url,errno,strerror(errno));
		}
		if (setsockopt (tcp_ctt->listen_fd, SOL_SOCKET, SO_REUSEADDR,
			(const char *) &(s->opt.tcp.reuse), sizeof(s->opt.tcp.reuse)) != 0){
			ms_errGoto(fail,  "setsockopt(SO_REUSEADDR) %s(%d,%s)", s->url,errno,strerror(errno));
		}
		int ret_bind=0;
		if(s->url_info.flag_useipv6){
			ret_bind=bind(tcp_ctt->listen_fd, (struct sockaddr *)&listen_addr6,sizeof(listen_addr6));
		}else{
			ret_bind=bind(tcp_ctt->listen_fd, (struct sockaddr *)&listen_addr,sizeof(listen_addr));	
		}
		if (ret_bind < 0){
			ms_errGoto(fail, "bind faild(%s):  %d %s", s->url_info.hostname,errno,strerror(errno));
		}
		if (listen(tcp_ctt->listen_fd, s->opt.tcp.backlog) < 0){
			ms_errGoto(fail, "listen faild(%s):  %d %s", s->url_info.hostname,errno,strerror(errno));
		}
	}else{
		/* Create our tcp listening socket. */
		if(s->url_info.flag_useipv6){
			inet_pton(AF_INET6,s->url_info.hostname,&listen_addr6.sin6_addr);
			s->fd = socket(AF_INET6, SOCK_STREAM, 0);	
		}else{
			listen_addr.sin_addr.s_addr=inet_addr(s->url_info.hostname);
			s->fd = socket(AF_INET, SOCK_STREAM, 0);	
		}
		if (s->fd < 0){
			ms_errGoto(fail, "socket %s(%d,%s)", s->url,errno,strerror(errno));
		}	
		if(1==s->opt.tcp.block
			&&s->opt.tcp.open_timeout>0){
			ms_waring("TCP(socket) is block,the open_timeout isnor effect");
		}
		if(ms_null!=s->ifcIn
			&& ms_buflen(s->ifcIn)){
			ms_debug("msnet_api_bindIface,interfacein:%s ",s->ifcIn);
			if(msnet_api_bindIface(s->fd,s->ifcIn)<0){
				ms_errGoto(fail, "msnet_api_bindIface failed %s(%d,%s) ", s->url, errno, strerror(errno));
			}
		}
		ms_u64 timeout=(ms_u64)s->opt.tcp.open_timeout*100000;
		if(s->opt.tcp.flag_scanport){
			timeout=(ms_u64)s->opt.tcp.open_timeout*1000;
		}
		if (0!=s->opt.tcp.block){
			ms_debug("[%s]-------------blocking", s->url);
			struct timeval  timeout={s->opt.tcp.rw_timeout/1000,0};
			if (setsockopt(s->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval)) < 0) {
				ms_close(s->fd);
				ms_errGoto(fail, "setsockopt(SO_RCVTIMEO) %s(%d,%s)", s->url, errno, strerror(errno));
			}	
			if (setsockopt(s->fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(struct timeval)) < 0) {
				ms_close(s->fd);
				ms_errGoto(fail, "setsockopt(SO_SNDTIMEO) %s(%d,%s)", s->url, errno, strerror(errno));
			}
		}
		ms_s08 ret_con=0;
		if(s->url_info.flag_useipv6){
			ret_con=msnet_api_socketConnect(s->fd, (struct sockaddr *)&listen_addr6, sizeof(listen_addr6),timeout,!s->opt.tcp.flag_noprint);
		}else{
			ret_con=msnet_api_socketConnect(s->fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr),timeout,!s->opt.tcp.flag_noprint);
		}
		if (ret_con< 0){
			if(s->opt.tcp.flag_scanport){
				goto ms_retryip;
			}else{
				ms_errGoto(ms_retryip, "connect faild(%s),hostname:%s ,(errno:%d,%s),goto ms_retryip", s->url,s->url_info.hostname,errno,strerror(errno));
			}
		}
		
		if(s->opt.tcp.recv_buffer_size>0){
			int old_bufsize	=0;	
			int len=sizeof(old_bufsize);
			getsockopt(s->fd , SOL_SOCKET, SO_RCVBUF, (char *)&old_bufsize, (socklen_t * __restrict__)&len);
			if(old_bufsize!=s->opt.tcp.recv_buffer_size){
				if (setsockopt(s->fd , SOL_SOCKET, SO_RCVBUF, 
					(const char *)&s->opt.tcp.recv_buffer_size, 
					sizeof(s->opt.tcp.recv_buffer_size)) < 0) {
					ms_errGoto(fail, "setsockopt(SO_RECVBUF) %s(%d,%s)", s->url, errno, strerror(errno));
				}			
				ms_debug("change SO_RCVBUF form %d to %d",old_bufsize,s->opt.tcp.recv_buffer_size);
			}
		}
		if(s->opt.tcp.send_buffer_size>0){
			int old_bufsize	=0;	
			int len=sizeof(old_bufsize);
			getsockopt(s->fd , SOL_SOCKET, SO_SNDBUF, (char *)&old_bufsize, (socklen_t * __restrict__)&len);
			if(old_bufsize!=s->opt.tcp.send_buffer_size){
				if (setsockopt(s->fd , SOL_SOCKET, SO_SNDBUF, 
					(const char *)&s->opt.tcp.send_buffer_size, 
					sizeof(s->opt.tcp.send_buffer_size)) < 0) {
					ms_errGoto(fail, "setsockopt(SO_SNDBUF) %s(%d,%s)", s->url, errno, strerror(errno));
				}			
				ms_debug("change SO_SNDBUF form %d to %d",old_bufsize,s->opt.tcp.send_buffer_size);
			}
		}
		if(setsockopt(s->fd, IPPROTO_TCP, TCP_NODELAY, 
			(void * __restrict__)&s->opt.tcp.nodelay, sizeof(s->opt.tcp.nodelay)) ){
			ms_errGoto(fail, "setsockopt(TCP_NODELAY) %s(%d,%s)", s->url, errno, strerror(errno));
		}
	}
	msepoll_add(tcp_ctt->msepoll_fd,s->fd,msepollin);
	s->is_connected=1;
	return 0;
fail:
	if(NULL!=tcp_ctt){
		if((1==s->opt.tcp.listen)&&( tcp_ctt->listen_fd>0)){	
			ms_close(tcp_ctt->listen_fd);
		}
		if(tcp_ctt->msepoll_fd>0){
			msepoll_close(&tcp_ctt->msepoll_fd);
		}
		ms_free(tcp_ctt);
		tcp_ctt=NULL;
	}
	if(s->fd>0){
		ms_close(s->fd);	
		msnet_api_deinit();
		s->fd=-1;
	}
	s->is_connected=0;
	return -1;
}
static int tcp_accept(URLContext *s)
{
	TCPContext * tcp_ctt=s->priv_data;
	if(s->url_info.flag_useipv6){
		ms_verbose("ipv6-accept");
		s->peer_info.len=sizeof(s->peer_info.sockaddrs6);	
		s->fd = accept(tcp_ctt->listen_fd, (struct sockaddr *)&s->peer_info.sockaddrs6, &s->peer_info.len);
		ms_byte ipaddrbuf[256]={0};
		msnet_api_getIpaddr((struct sockaddr *)&s->peer_info.sockaddrs6,ipaddrbuf);
		ms_verbose("New client from %s,flags:%d",ipaddrbuf,s->flags);
	}else{
		ms_verbose("ipv4-accept");
		s->peer_info.len=sizeof(s->peer_info.sockaddrs);	
		s->fd = accept(tcp_ctt->listen_fd, (struct sockaddr *)&s->peer_info.sockaddrs, &s->peer_info.len);
		ms_verbose("New client from %s,flags:%d", inet_ntoa(s->peer_info.sockaddrs.sin_addr),s->flags);
	}
	if (s->fd<0){
		ms_close(s->fd);
		ms_errRet(-1, "accept error:  %d %s", errno, strerror(errno));
	}
	if(s->opt.tcp.recv_buffer_size>0){
		int old_bufsize	=0;	
		int len=sizeof(old_bufsize);
		getsockopt(s->fd , SOL_SOCKET, SO_RCVBUF, (char *)&old_bufsize, (socklen_t * __restrict__)&len);
		if(old_bufsize!=s->opt.tcp.recv_buffer_size){
			if (setsockopt(s->fd , SOL_SOCKET, SO_RCVBUF, 
				(const char *)&s->opt.tcp.recv_buffer_size, 
				sizeof(s->opt.tcp.recv_buffer_size)) < 0) {
				ms_errRet(-1, "setsockopt(SO_RECVBUF) %s(%d,%s)", s->url, errno, strerror(errno));
			}			
			ms_debug("change SO_RCVBUF form %d to %d",old_bufsize,s->opt.tcp.recv_buffer_size);
		}
	}
	if(s->opt.tcp.send_buffer_size>0){
		int old_bufsize	=0;	
		int len=sizeof(old_bufsize);
		getsockopt(s->fd , SOL_SOCKET, SO_SNDBUF, (char *)&old_bufsize, (socklen_t * __restrict__)&len);
		if(old_bufsize!=s->opt.tcp.send_buffer_size){
			if (setsockopt(s->fd , SOL_SOCKET, SO_SNDBUF, 
				(const char *)&s->opt.tcp.send_buffer_size, 
				sizeof(s->opt.tcp.send_buffer_size)) < 0) {
				ms_errRet(-1, "setsockopt(SO_SNDBUF) %s(%d,%s)", s->url, errno, strerror(errno));
			}			
			ms_debug("change SO_SNDBUF form %d to %d",old_bufsize,s->opt.tcp.send_buffer_size);
		}
	}
	if (0==s->opt.tcp.block){
		if (msnet_api_socketNonblock(s->fd, 1) < 0){
			ms_errRet(-1, "msnet_api_socketNonblock %d failed",s->fd);
		}
		ms_verbose("set client fd to nonblock");
	}else{
		ms_debug("set client fd to block");
	}
	return s->fd;	
}

static int tcp_read(URLContext *s ,  unsigned char *buf, int size)
{
	if(0==s->is_connected){
		return -1;
	}
	TCPContext * tcp_ctt=s->priv_data;
	 if (0==s->opt.tcp.block){
		MSEPOLLRet ret_epoll=msepoll_waitfd_timeout_noopt(tcp_ctt->msepoll_fd, s->fd, 0, s->opt.tcp.rw_timeout);
		if (ret_epoll < 0){
			if(msepoll_ret_err_inner==ret_epoll){ms_error("[%s]msepoll error: %d %s",s->url,errno, strerror(errno));}
			return ret_epoll;
		}
	 }	
	int ret = recv(s->fd, buf, size, 0);	
	if (ret<0){
		ms_close(s->fd);
		ms_errRet(-1, "recv error(%d): %d %s", ret,errno, strerror(errno));
	}
	return ret;
}
static int tcp_write(URLContext *s, unsigned char *buf, int size)
{
	if(0==s->is_connected){
		return -1;
	}
	TCPContext * tcp_ctt=s->priv_data;
	int ret=0;
	if (0==s->opt.tcp.block){
		#if 0
		MSEPOLLRet ret_epoll=msepoll_waitfd_timeout(tcp_ctt->msepoll_fd, s->fd, 1, s->opt.tcp.rw_timeout);
		if (ret_epoll < 0){
			if(msepoll_ret_err_inner==ret_epoll){ms_error("msepoll error:%s",s->url);}
			return ret_epoll;
		}
		#endif
	}
	ret = send(s->fd, buf, size, 0);
	if (ret<0){
		ms_close(s->fd);
		ms_errRet(-1, "send error:%d %s,url:%s", errno, strerror(errno),s->url);
	}
	return ret;
}
static int tcp_get_listen_handle(URLContext *s)
{
	if(0==s->is_connected){
		return -1;
	}
	TCPContext * tcp_ctt=s->priv_data;
	return tcp_ctt->listen_fd;
}

static int tcp_close(URLContext *s)
{
	if(0==s->is_connected){
		return 0;
	}
	TCPContext * tcp_ctt=s->priv_data;
	msepoll_del(tcp_ctt->msepoll_fd,s->fd,msepollin);
	msepoll_close(&tcp_ctt->msepoll_fd);
	if((NULL!=tcp_ctt)
		&&(1==s->opt.tcp.listen)
		&& tcp_ctt->listen_fd>0	){	
		ms_close(tcp_ctt->listen_fd);
	}
	if(NULL!=tcp_ctt){
		ms_free(tcp_ctt);
		tcp_ctt=NULL;
	}
	if(s->fd>0){
		ms_close(s->fd);	
		msnet_api_deinit();
		s->fd=-1;
	}
	s->is_connected=0;
	return 0;
}
static int tcp_shutdown(URLContext *s)
{
	int how;
	if(s->flags & FLAG_WRITE){
		how=SHUT_WR;
	}else{
		how=SHUT_RDWR;
	}
	return shutdown(s->fd, how);
}
static int tcp_get_window_size(URLContext *s)
{
	int avail;
	int avail_len = sizeof(avail);
	if (getsockopt(s->fd, SOL_SOCKET, SO_RCVBUF, &avail, &avail_len)) {
		return -1;
	}
	return avail;
}
URLProtocol ffurl_tcp_protocol={
	.nameDes	= msptc_getPtcNameDes(msptc_tcp),
    .url_open	= tcp_open,
    .url_read	= tcp_read,
    .url_write	= tcp_write,
    .url_close	= tcp_close,	
    .priv_func={
		.tcp.accept				=tcp_accept,
		.tcp.get_listen_handle	=tcp_get_listen_handle,
		.tcp.shutdown			=tcp_shutdown,
		.tcp.get_window_size	=tcp_get_window_size,
	},    
    .flags		=STREAM_LIVE,
    .next	=NULL,
};
#undef TCP_C
